<!--
 * @Author: Penk
 * @LastEditors: Penk
 * @LastEditTime: 2021-07-12 00:23:30
 * @FilePath: \temp\myVue.html
-->
<!--
 * @Author: Penk
 * @LastEditors: Penk
 * @LastEditTime: 2021-04-12 17:32:34
 * @FilePath: \test_html\myVue.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    #app {
      text-align: center;
      margin: 100px auto auto auto;
    }
  </style>
</head>

<body>
  <div id="app">
    <div v-html='msg'></div>
    <input v-model='author.name' style="margin-bottom:20px;">
    <br>
    姓名：{{author.name}}
    <br>
    计算属性变大写：{{toUpperCaseName}}
    <br>
    <br>
    <button v-on:click='change(author.name,"自定义参数")'>test</button>
  </div>
  <!-- <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> -->

  <!-- <script src="./script.js"></script> -->
  <script>
    class Penk {
      constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let methods = options.methods;
        let computed = options.computed;

        if (this.$el) {
          // 先生成发布者
          new Observer(this.$data);

          // 设置代理，过滤$data
          this.proxyData(this.$data);

          // 设置methods
          this.proxyMethods(methods);

          // 设置computed
          this.proxyComputed(computed);

          // 再生成订阅者
          new Compiler(this.$el, this);

          // 执行挂在
          options.mounted.call(this.$data);
        }
      }

      proxyData(data) {
        for (let key in data) {
          Object.defineProperty(this, key, {
            enumerable: true,
            get() {
              return this.$data[key];
            },
            set(newVal) {
              if (this.$data[key] != newVal) {
                this.$data[key] = newVal;
              }
            }
          })
        }
      }

      proxyMethods(methods) {
        for (let key in methods) {
          this.$data[key] = methods[key];
        }
      }

      proxyComputed(computed) {
        for (let key in computed) {
          // this.$data[key] = computed[key].call(this);
          Object.defineProperty(this.$data, key, {
            get: () => {
              return computed[key].call(this);
            }
          })
        }
      }
    }

    // 观察者
    class Watcher {
      constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        this.oldValue = this.get();
      }
      get() {
        Dep.target = this;
        let val = CompileUtils.getVal(this.expr, this.vm);
        Dep.target = null;
        return val;
      }
      update() {
        let newVal = CompileUtils.getVal(this.expr, this.vm);
        if (this.oldValue !== newVal) {
          this.cb(newVal);
        }
      }
    }

    // 订阅者
    class Dep {
      constructor() {
        this.subs = [];
      }
      // 订阅
      addSub(watcher) {
        this.subs.push(watcher)
      }
      // 发布
      notify() {
        this.subs.forEach(watcher => watcher.update());
      }
    }

    // 编译者
    class Compiler {
      constructor(el, vm) {
        this.el = this.getElementByEl(el);
        this.vm = vm;

        // 获取dom节点
        let fragment = this.node2fragment(this.el);

        // 编译模板 用数据编译
        this.compile(fragment);

        // 把内容塞到页面中
        this.el.appendChild(fragment);
      }
      // 核心编译方法
      compile(node) {
        let childNodes = node.childNodes;
        [...childNodes].forEach(e => {
          if (e.nodeType == 1) {
            this.compileElement(e);
          } else if (e.nodeType == 3) {
            this.compileText(e);
          }
        })
      }
      // 编译文本
      compileText(node) {
        let text = node.textContent;
        if (/\{\{(.*)\}\}/.test(text))
          CompileUtils.text(node, text, this.vm);
      }
      // 编译元素
      compileElement(node) {
        this.compile(node);
        let attributes = node.attributes;
        [...attributes].forEach(attr => {
          let {
            name,
            value
          } = attr;
          if (this.isDirective(name)) {
            let [, directive] = name.split('-');
            let [directiveName, eventName] = directive.split(':');
            CompileUtils[directiveName](node, value, this.vm, eventName);
          }
        })
      }
      // 判断是否指令
      isDirective(attrName) {
        return attrName.startsWith('v-');
      }
      // 节点转片段
      node2fragment(el) {
        let fragment = document.createDocumentFragment();
        let node;
        while (node = el.firstChild) {
          fragment.appendChild(node);
        }
        return fragment;
      }
      // 获取元素
      getElementByEl(el) {
        if (el.nodeType === 1) return el;
        return document.querySelector(el);
      }
    }

    // 编译工具
    var CompileUtils = {
      getVal(expr, vm) {
        let data = vm.$data;
        expr.split('.').forEach(e => {
          data = data[e];
        })
        return data;
      },
      setVal(expr, vm, val) {
        let data = vm.$data;
        expr.split('.').reduce((total, currentValue, index, arr) => {
          if (index == arr.length - 1) {
            total[currentValue] = val;
            return;
          }
          return total[currentValue];
        }, data);
      },
      getContentValue(expr, vm) {
        let value = expr.replace(/\{\{(.*)\}\}/g, (...args) => {
          return this.getVal(args[1], vm);
        })
        return value;
      },
      getMethodObj(expr, vm) {
        console.log(expr);
        let leftIndex = expr.indexOf('(');
        let method = expr.slice(0, leftIndex);
        let params = expr.slice(leftIndex + 1, expr.length - 1).split(',');
        vm.$data[method]().call(this, ...params);
        return {
          method
        }
      },
      // 指令
      model(node, expr, vm) {
        let value = this.getVal(expr, vm);
        let fn = this.update.modelUpdater;
        fn(node, value);
        new Watcher(vm, expr, (newVal) => {
          fn(node, newVal);
        })
        node.addEventListener('input', e => {
          let val = e.target.value;
          this.setVal(expr, vm, val);
        });
      },
      html(node, expr, vm) {
        let value = this.getVal(expr, vm);
        let fn = this.update.htmlUpdater;
        fn(node, value);
        new Watcher(vm, expr, (newVal) => {
          fn(node, newVal);
        })
      },
      // 事件绑定
      on(node, expr, vm, eventName) {
        node.addEventListener(eventName, () => {
          let leftIndex = expr.indexOf('(');
          let method = expr.slice(0, leftIndex);
          let params = expr.slice(leftIndex + 1, expr.length - 1).split(',');
          let temParams = [];
          params.forEach(param => {
            if (param.indexOf("'") == 0 || param.indexOf('"') == 0) {
              param
              temParams.push(param.slice(1, param.length - 1));
            } else {
              temParams.push(this.getVal(param, vm));
            }
          })
          vm.$data[method].call(this, ...temParams);
        })
      },
      text(node, expr, vm) {
        let fn = this.update.textUpdater;
        let value = expr.replace(/\{\{(.*)\}\}/g, (...args) => {
          new Watcher(vm, args[1], (newVal) => {
            fn(node, this.getContentValue(expr, vm));
          })
          return this.getVal(args[1], vm);
        })
        fn(node, value);
      },
      // 更新视图方法
      update: {
        modelUpdater(node, value) {
          node.value = value;
        },
        htmlUpdater(node, value) {
          node.innerHTML = value;
        },
        textUpdater(node, value) {
          node.textContent = value;
        }
      }
    }

    // 数据劫持
    class Observer {
      constructor(data) {
        //初始化时候劫持数据
        this.observer(data)
      }
      observer(data) {
        if (data && typeof data == 'object') {
          for (let key in data) {
            this.defineReactive(data, key, data[key]);
          }
        }
      }
      defineReactive(obj, key, val) {
        this.observer(val);
        let dep = new Dep();
        Object.defineProperty(obj, key, {
          enumerable: true,
          get() {
            Dep.target && dep.addSub(Dep.target);
            return val;
          },
          set: (newVal) => {
            if (val == newVal) return;
            val = newVal;
            // 重新赋值的时候劫持数据
            this.observer(newVal);
            dep.notify();
          }
        })
      }
    }</script>
  <script>
    let vm = new Penk({
      el: '#app',
      data: {
        author: {
          name: 'penk',
          age: 18,
          a: { aa: 1 }
        },
        msg: '<h1>v-html</h1>'
      },
      methods: {
        change(...data) {
          alert('method,带参~' + data);
        }
      },
      mounted() {
        // this.change('mounted');
      },
      computed: {
        toUpperCaseName() {
          return this.author.name.toUpperCase();
        }
      }
    });
  </script>
</body>

</html>